Repository navigation
fix(shader): LDS float atomics quiet signalling NaNs and flush denormals as the hardware does - #2312
Conversation
|
The overlap with #2162 is one TechnicalDebt line: both PRs add a sentence to the f32 denormals entry (this one for the LDS float atomics, #2162 for AI-assisted: yes (Claude Code). |
|
Native Windows run, since no Windows results were posted. Setup: Windows 10 (19045), RTX 3060 Laptop (NVIDIA 616.92), MinGW-w64 GCC 15.2.0 (WinLibs r7), Release. Head
No title was run. 🤖 Addressed by Claude Code |
|
Tested on Linux (CachyOS x86_64, Intel RPL-S iGPU / Mesa 26.3.0-devel / Vulkan 1.4.363, GCC 16.2.1) at
LGTM! |
adriadam10
left a comment
There was a problem hiding this comment.
Measured on RDNA2 (gfx1035) with tools/hw-oracle: ds_min/max/cmpst(_rtn)_f32 and _f64 over 15 × 15 operand pairs (±0, ±1, 2, ±inf, ±smallest and largest denormal, quiet/signalling NaNs with payloads), all nine memory/returned results per pair, IEEE 0 and 1, and every value 0-3 of the type's denormal field (f32 field for the f32 forms, f16/f64 field for the f64 forms). Your rule matches the hardware bit for bit for fields 0, 1 and 3 in both IEEE modes. Locally the build, the LDS tests (RADV and CI's lavapipe, 7.2 s / 3.4 s) and the negative checks (both tests fail without the change) pass.
One thing to change:
-
SharedMemoryInstructions.cpp:220and:224flush only for field 0, and TechnicalDebt (TechnicalDebt.md:366) says fields 1 and 2 are "treated as keeping denormals". Field 2 (FP_DENORM_FLUSH_IN) flushes both operands on the hardware, exactly like field 0: 228 of 2025 results per mode differ from this PR, the same in IEEE 0 and 1 and for f32 and f64, e.g.ds_max_f32, old0x00000000, data0x00000001: hardware0x00000000, PR0x00000001ds_min_f32, old0x00000000, data0x80000001: hardware0x80000000, PR0x80000001ds_cmpst_f32, old0x00000000, compare0x00000001: hardware stores data1, PR leaves0x00000000ds_max_f64, old0, data0x0000000000000001: hardware0, PR1
Field 1 (
FP_DENORM_FLUSH_OUT) keeps them, as the PR does. So the flush is on input: flush when bit 0 of the field is clear ((field & 1u) == 0u). A test row with field 2 (and one with field 1) for both types would pin it, and the TechnicalDebt sentence can then drop "denormal field values 1 and 2" from "Not measured".
|
Thanks, confirmed here. New commit 59e1899 on top of 3e8d755: the flush decision for the LDS float atomics is now Measured on RDNA2 (gfx1036, Ryzen 7950X3D iGPU) with
Field 2 gives the same results as field 0 and field 1 the same as field 3 on all 196 pairs, in both IEEE modes and for both types, e.g. Changes:
Tested on Linux (CachyOS, kernel 7.2.9, GCC 16.2.1), Release,
Data and script (132 is over the PR's 14 × 14 set, not your 15 × 15): https://github.com/SleglJan/AnyPS5/tree/audit/oracle-2026-10-08/oracle-audit/lds-denorm-fields AI-assisted: yes (Claude Code) |
59e1899 to
edb53ae
Compare
|
Rebased onto Linux (CachyOS, kernel 7.2.9, GCC 16.2.1), Release, head edb53ae: AI-assisted: yes (Claude Code) |
…als as the hardware does ds_min/max/cmpst_f32 and ds_min/max/cmpst_f64 with their _rtn forms follow the rule measured on RDNA2 (gfx1036): both operands are flushed to a signed zero when the type's denormal field flushes, a signalling-NaN data operand is stored quieted, else a signalling-NaN old value, else a quiet NaN loses, else the ordered compare decides, and the flushed value is written. The shared f32 path used the host's float compares, so its NaN and denormal results were the driver's; the shared f64 path lacked the signalling-NaN rules the buffer path has. Both now use AtomicFloatBits. The translation marks the shared float atomics with the mode's flush in MemoryInfo. The new agc_driver_lds_float_atomic_modes pins 32 operand pairs per type in the titles' mode, with both fields kept and with both flushed. The 64-bit test's model gets the signalling-NaN rule and one lane kind now feeds the signalling NaN of its table into min/max. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012GLfKH7Sqe51wX254k1XsV
…well On RDNA2 (gfx1036) ds_min/max/cmpst(_rtn)_f32 and _f64 flush both operands when bit 0 of the type's denormal field is clear: field 2 flushes like field 0, field 1 keeps like field 3, in IEEE 0 and 1. The flush decision now tests that bit. agc_driver_lds_float_atomic_modes adds FLOAT_MODE 0x50 (both fields 1) and 0xa0 (both fields 2), and the TechnicalDebt entry records the measured fields. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012GLfKH7Sqe51wX254k1XsV
edb53ae to
4fb613e
Compare
adriadam10
left a comment
There was a problem hiding this comment.
Checked on the PR merged onto current main: build and the libSceAgc/libSceAgcDriver ctest pass, including agc_driver_lds_atomics64 and agc_driver_lds_float_atomic_modes; both fail with the non-test changes reverted. The flush rule (field & 1) == 0 for fields 0-3 matches what I measured on gfx1035 at IEEE 0 and 1. The code and tests are unchanged since the head I measured; the new head only rebases the f32 denormals TechnicalDebt line. Merging.
|
Rebased again, onto Linux (CachyOS, kernel 7.2.9, GCC 16.2.1), Release, head 4fb613e: AI-assisted: yes (Claude Code) |
What
Follow-up to #573 (the 64-bit LDS atomics), whose TechnicalDebt entry said the f64 forms were not measured on hardware.
ds_min/max_f32,ds_cmpst_f32,ds_min/max_f64,ds_cmpst_f64and their_rtnforms now follow the rule measured on RDNA2 (gfx1036, Ryzen 7950X3D iGPU) withtools/hw-oracle, over 14 × 14 operand sets per type (±0, ±1, ±inf, ±smallest denormal, quiet and signalling NaNs of both signs and with payloads) in IEEE 0 and 1 with every value 0-3 of the type's denormal field (fields 1 and 2 added after review); every memory and returned result of the six f64 and nine f32 results per pair, in each of the eight modes, 0 mismatches:FLOAT_MODEbits 5:4 for f32, bits 7:6 for f64; a shader without a known mode flushes f32 and keeps f64, as elsewhere);cmpstcompares the flushed bits (both zeros equal, NaN never equal) and leaves memory unchanged when they differ;IEEE_MODEhas no effect.This is the rule
AtomicFloatBits::minMaxalready implements for the buffer float atomics, plus the flush. The shared f32 path usedOpIsNan/OpFOrdLessThan/OpFOrdEqualon the bit-cast values, so its NaN and denormal results were the driver's (both RADV and NVIDIA quiet nothing and flush f32 denormals inside those compares in every mode); the shared f64 path (#573) ordered the bits but had none of the signalling-NaN rules. Both now useAtomicFloatBits; the translation marks the shared float atomics with the mode's flush inMemoryInfo::flushDenormals. The 29 integer 64-bit forms, EXEC masking, immediate offsets, out-of-range addresses (0, no write) and contention (256 lanes, serial order) were measured as well and match the recompiler already;ds_add_f32is not measured and is unchanged.TechnicalDebt: the 64-bit entry loses its "not measured" sentence and gains the measured facts about mixed-width serialisation (hardware serialises 32-bit and 64-bit atomics on one address, the lock does not: in the measurement RADV lost 64 of 128 increments in wave32 and 32 in wave64, NVIDIA 48 in wave32, timing-dependent) and the misaligned fault; a new LDS float atomics entry records the rule; the f32 denormals entry points to it.
Measurement data, scripts and the host comparison before and after the change: https://github.com/SleglJan/AnyPS5/tree/audit/oracle-2026-10-08/oracle-audit/lds64 (
report.mdthere is the summary).Overlap: #2162 adds a sentence to the same f32 denormals TechnicalDebt entry (its
v_fma_mixlo_f16/v_fma_mixhi_f16flush rule), and #2025 and #1290 add entries next to the edited ones; none of them shares code with this PR or is built on by it, so whichever merges second rebases the one line. No open pull request changes the shared float atomics.Tested
Linux (CachyOS, kernel 7.2.9, GCC 16.2.1), Release,
ANYPS5_ENABLE_SPIRV_TOOLS=ON, RTX 3080 (615.71.09) and RADV (Mesa 26.2.4), on this branch (based onmaind70b899).agc_driver_lds_float_atomic_modes: 32 operand pairs per type (the classes above, both orders), each lane runs the six f32 and six f64 forms on its LDS slot; 27 result dwords per lane pinned bit for bit against the oracle in the titles' mode (0xc0, also used for "no float mode"), with both fields kept (0xf0), both flushed (0x00), both at 1 (0x50, kept) and both at 2 (0xa0, flushed). Written first: it failed onmain's lowering at lane 7 (ds_min_rtn_f32, flushed denormal) and passes with the change.agc_driver_lds_atomics64: its CPU model gets the signalling-NaN rule, and the lanes of kind 2 now take their data fromfloats[(tid / 4 + 9) % 12], which feeds the table's signalling NaN, −inf and the negative denormal into min/max (the old index never reached entry 9). It failed onmainat lane 2 (ds_min_f64, the signalling-NaN data not stored quieted) and passes with the change.agc_driver_lds_atomics,agc_driver_lds_integer_atomics,agc_driver_lds_condxchg,agc_driver_lds_read_write,agc_driver_lds_src2pass on both GPUs; LDS tests--repeat until-fail:3on both; fullctest516/517 on each GPU; the one failure differs per GPU and runs no LDS instruction:agc_driver_storage_cache_budgeton the iGPU (its memory budget) andagc_driver_dcc_keys_first_writeon the RTX 3080 ("an unchanged pending image was uploaded again", also reported in several open pull requests today).tools/check_conventions.py --base upstream/main,tools/check_test_registration.pypass.agc_driver_lds_float_atomic_modesandagc_driver_lds_atomics64pass three repeats on RADV and the RTX 3080; the new 0xa0 mode fails without the change (lane 7,ds_max_rtn_f32); conventions and test registration pass. Fork Build on head 59e1899 merged intomaina291ae0, run 38030543364: relinker green on all three platforms, linux (lavapipe) 539 passed, 29 skipped, 1 failed of 569 with both LDS tests passing (the failure isguest_video_out_resolution_statusaborts on the linux CI job since #1714 merged:sceVideoOutOpencreates the window (#1367) and the test has no display guard #2509 on that base, fixed onmainsince); windows stops at Windows build fails onmain:duplicate patched export name: LibwuIonIBwin libSceVideoOut (since #1339) #2462's build break on that base.maind70b899, run 37947911451: Windows builds and passes 508/508 with the AGC execution tests skipped (no Vulkan device on the runner, as for every execution test); the Linux job runs the new test on lavapipe (3.3 s) and passes 517/517; the relinker matrix passes on all three systems..mainf8c2f07):agc_driver_lds_atomics64andagc_driver_lds_float_atomic_modespass; full suite 512 of 513, the one failure an intermittent test from fix(agc): storage access to a depth surface's stencil plane through its stencil aspect #2325.Checklist
main; no other open PR implements the same functionsNotImplemented_nid_no_patch); silent stubs are listed in TechnicalDebt.mdfiles or images added to the repository (attach them to this PR)🤖 Generated with Claude Code